package com.game2048

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.draggable2D
import androidx.compose.foundation.gestures.rememberDraggable2DState
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextMeasurer
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

import java.util.Random

import kotlin.math.abs

// 游戏
class Game {
    // 状态
    enum class Status {
        END,        // 已结束
        LOST,       // 游戏失败
        PLAYING,    // 游戏中
        WON         // 游戏胜利
    }

    private lateinit var mNumbers:
        SnapshotStateList<SnapshotStateList<Int>>       // 数字矩阵
    private val mRandom: Random                         // 随机数生成器
    private lateinit var mStatus: MutableState<Status>  // 状态
    private lateinit var mTextMeasurer: TextMeasurer    // 文字测量者
    private lateinit var mTextStyle: TextStyle          // 文字风格

    /**
     * 构造方法。
     */
    constructor() {
        // 初始化成员
        mRandom = Random()
    }

    /**
     * 移动之后要做的事。
     * @param changed   指示移动之后，每个格上的数字是否改变。任意一格有改变则为 true，否则为 false
     */
    private fun afterMove(changed: Boolean) {
        // 如果胜利或失败，则游戏结束
        if (this.isWon()) {
            this.gameOver(Status.WON)
            return
        }
        if (this.isLost()) {
            this.gameOver(Status.LOST)
            return
        }

        // 创建下一个数
        if (changed) {
            this.createRandomNumber()
        }
    }

    /**
     * 在所有格中，随机找到未被使用的一个，将格上的数字设为2或4。
     */
    private fun createRandomNumber() {
        var row = mRandom.nextInt(Const.GAME_BOARD_GRID_COUNT_V)
        var line = mRandom.nextInt(Const.GAME_BOARD_GRID_COUNT_H)

        while (mNumbers[row][line] != 0) {
            row = mRandom.nextInt(Const.GAME_BOARD_GRID_COUNT_V)
            line = mRandom.nextInt(Const.GAME_BOARD_GRID_COUNT_H)
        }

        mNumbers[row][line] = if (mRandom.nextInt(8) <= 3) 2 else 4
    }

    /**
     * 画所有数字。
     * @param drawer    绘图者
     */
    private fun drawAllNumbers(drawer: DrawScope) {
        for (i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V) {
            for (j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H) {
                if (mNumbers[i][j] != 0) {
                    this.drawOneNumber(drawer, j, i, mNumbers[i][j])
                }
            }
        }
    }

    /**
     * 画游戏区域。
     * @param drawer        绘图者
     * @param gameBoardSize 游戏区域尺寸
     */
    private fun drawGameBoard(drawer: DrawScope, gameBoardSize: Float) {
        // 画边框
        drawer.drawRect(
            color = Color.Black,
            style = Stroke(width = Const.GAME_BOARD_PEN_WIDTH),
            topLeft = Offset(Const.GAME_BOARD_PEN_WIDTH, Const.GAME_BOARD_PEN_WIDTH),
            size = Size(gameBoardSize - Const.GAME_BOARD_PEN_WIDTH, gameBoardSize - Const.GAME_BOARD_PEN_WIDTH)
        )

        // 画竖线
        for (i in 1 ..< Const.GAME_BOARD_GRID_COUNT_V) {
            drawer.drawLine(
                color = Color.Black,
                strokeWidth = Const.GAME_BOARD_PEN_WIDTH,
                start = Offset((Const.GRID_SIZE + Const.GAME_BOARD_PEN_WIDTH) * i + Const.GAME_BOARD_PEN_WIDTH, 1f),
                end = Offset((Const.GRID_SIZE + Const.GAME_BOARD_PEN_WIDTH) * i + Const.GAME_BOARD_PEN_WIDTH, gameBoardSize)
            )
        }

        // 画横线
        for (i in 1 ..< Const.GAME_BOARD_GRID_COUNT_H) {
            drawer.drawLine(
                color = Color.Black,
                strokeWidth = Const.GAME_BOARD_PEN_WIDTH,
                start = Offset(1f, (Const.GRID_SIZE + Const.GAME_BOARD_PEN_WIDTH) * i + Const.GAME_BOARD_PEN_WIDTH),
                end = Offset(gameBoardSize, (Const.GRID_SIZE + Const.GAME_BOARD_PEN_WIDTH) * i + Const.GAME_BOARD_PEN_WIDTH)
            )
        }
    }

    /**
     * 画单个数字。
     * @param drawer    绘图者
     * @param line      列
     * @param row       行
     * @param number    要画的数字
     */
    private fun drawOneNumber(drawer: DrawScope, line: Int, row: Int, number: Int) {
        // 计算位置
        val text = number.toString()
        val textLayoutResult = mTextMeasurer.measure(text)
        val x = line * (Const.GRID_SIZE + Const.GAME_BOARD_PEN_WIDTH) + Const.GRID_SIZE / 2 - textLayoutResult.size.width
        val y = row * Const.GRID_SIZE + (Const.GRID_SIZE -
            textLayoutResult.firstBaseline - textLayoutResult.lastBaseline) / 2 - Const.GAME_BOARD_PEN_WIDTH

        // 画数字
        drawer.drawText(
            textMeasurer = mTextMeasurer,
            text = text,
            topLeft = Offset(x, y),
            style = mTextStyle
        )
    }

    /**
     * 创建游戏区域。
     * @param gameBoardSizePx   游戏区域尺寸，单位：像素
     */
    @OptIn(ExperimentalFoundationApi::class)
    @Composable
    fun GameBoard(gameBoardSizePx: Float) {
        // 初始化成员
        mStatus = remember {
            mutableStateOf(Status.END)
        }
        mTextStyle = TextStyle(fontSize = Const.GRID_SIZE.sp * Const.SCALE_GAME_BOARD_TEXT_SIZE)
        mTextMeasurer = rememberTextMeasurer()

        // 创建数字矩阵
        mNumbers = remember {
            mutableStateListOf<SnapshotStateList<Int>>()
        }
        for (i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V) {
            val subList = remember {
                mutableStateListOf<Int>()
            }
            repeat(Const.GAME_BOARD_GRID_COUNT_H) {
                subList.add(0)
            }
            mNumbers.add(i, subList)
        }

        // 创建游戏区域
        val gameBoardSizeDp = gameBoardSizePx / LocalDensity.current.density
        Canvas(Modifier
            .width(gameBoardSizeDp.dp)
            .height(gameBoardSizeDp.dp)
            .background(Const.GAME_BOARD_COLOR)
            .draggable2D(
                state = rememberDraggable2DState {},
                onDragStopped = {
                    if (abs(it.x) > abs(it.y)) {
                        if (it.x < 0) {
                            this.moveLeft()
                        } else {
                            this.moveRight()
                        }
                    } else if (it.y < 0) {
                        this.moveUp()
                    } else {
                        this.moveDown()
                    }
                }
            )
        ) {
            this@Game.drawGameBoard(this, gameBoardSizePx)
            this@Game.drawAllNumbers(this)
        }
    }

    /**
     * 游戏结束。
     * @param status    游戏状态
     */
    private fun gameOver(status: Status) {
        // 将状态设置为已结束
        mStatus.value = status
    }

    /**
     * 判断是否失败。
     * @return 失败则返回 true，否则返回 false
     */
    private fun isLost(): Boolean {
        // 横向检查所有数字
        for (i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V) {
            for(j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H - 1) {
                if (mNumbers[i][j] == 0 || mNumbers[i][j] == mNumbers[i][j + 1]) {
                    return false
                }
            }
        }

        // 纵向检查所有数字
        for (j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H) {
            for (i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V - 1) {
                if (mNumbers[i][j] == mNumbers[i + 1][j]) {
                    return false
                }
            }
        }

        // 判断最右边一列是否有零
        for (i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V) {
            if (mNumbers[i][3] == 0) {
                return false
            }
        }

        return true
    }

    /**
     * 判断是否胜利。
     * @return 胜利则返回 true，否则返回 false
     */
    private fun isWon(): Boolean {
        // 检查所有数字
        for (i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V) {
            for (j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H) {
                if (mNumbers[i][j] == Const.WIN_NUMBER) {
                    return true
                }
            }
        }

        return false
    }


    /**
     * 下移。
     */
    private fun moveDown() {
        var ignore = -1     // 忽略的格
        var changed = false // 是否相加、移动

        // 从左到右遍历每一列
        for (j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H) {
            // 每列从上到下遍历每一格，判断是否有相同数，有则相加
            for (i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V) {
                // 如果该数为零或之前被加过，则什么也不做
                if (mNumbers[i][j] == 0 || i == ignore) {
                    continue
                }

                // 不为零则从该数下方逐个检查
                for (k in i + 1 ..< Const.GAME_BOARD_GRID_COUNT_V) {
                    // 遇到零跳过
                    if (mNumbers[k][j] == 0) {
                        continue
                    }

                    // 如果两数不相等，则结束本次循环
                    if (mNumbers[k][j] != mNumbers[i][j]) {
                        break
                    }

                    // 两数相等则相加
                    mNumbers[k][j] += mNumbers[i][j]
                    mNumbers[i][j] = 0
                    ignore = k
                    changed = true
                }
            }

            // 所有数字向下移动
            for (i in Const.GAME_BOARD_GRID_COUNT_V - 2 downTo 0) {
                // 如果该数为零（没有数），则什么也不做
                if (mNumbers[i][j] == 0) {
                    continue
                }

                // 不为零则向下移动
                for (k in i + 1 ..< Const.GAME_BOARD_GRID_COUNT_V) {
                    if (mNumbers[k][j] == 0) {
                        mNumbers[k][j] = mNumbers[k - 1][j]
                        mNumbers[k - 1][j] = 0
                        changed = true
                    }
                }
            }
        }

        this.afterMove(changed)
    }

    /**
     * 左移。
     */
    private fun moveLeft() {
        var ignore = -1 // 忽略的格
        var changed = false // 是否相加、移动

        // 从上到下遍历每一行
        for (i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V) {
            // 每行从右到左遍历每一格，判断是否有相同数，有则相加
            for (j in Const.GAME_BOARD_GRID_COUNT_H - 1 downTo 1) {
                // 如果该数为零或之前被加过，则什么也不做
                if (mNumbers[i][j] == 0 || j == ignore) {
                    continue
                }

                // 不为零则从该数左面逐个检查
                for (k in j - 1 downTo 0) {
                    // 遇到零跳过
                    if (mNumbers[i][k] == 0) {
                        continue
                    }

                    // 如果两数不相等，则结束本次循环
                    if (mNumbers[i][k] != mNumbers[i][j]) {
                        break
                    }

                    // 两数相等则相加
                    mNumbers[i][k] += mNumbers[i][j]
                    mNumbers[i][j] = 0
                    ignore = k
                    changed = true
                }
            }

            // 所有数字向左移动
            for (j in 1 ..< Const.GAME_BOARD_GRID_COUNT_H) {
                // 如果该数为零（没有数），则什么也不做
                if (mNumbers[i][j] == 0) {
                    continue
                }

                // 不为零则向左移动
                for (k in j - 1 downTo 0) {
                    if (mNumbers[i][k] == 0) {
                        mNumbers[i][k] = mNumbers[i][k + 1]
                        mNumbers[i][k + 1] = 0
                        changed = true
                    }
                }
            }
        }

        this.afterMove(changed)
    }

    /**
     * 右移。
     */
    private fun moveRight() {
        var ignore = -1 // 忽略的格
        var changed = false // 是否相加、移动

        // 从上到下遍历每一行
        for (i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V) {
            // 每行从左到右遍历每一格，判断是否有相同数，有则相加
            for (j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H - 1) {
                // 如果该数为零或之前被加过，则什么也不做
                if (mNumbers[i][j] == 0 || j == ignore) {
                    continue
                }

                // 不为零则从该数右面逐个检查
                for (k in j + 1 ..< Const.GAME_BOARD_GRID_COUNT_H) {
                    // 遇到零跳过
                    if (mNumbers[i][k] == 0) {
                        continue
                    }

                    // 如果两数不相等，则结束本次循环
                    if (mNumbers[i][k] != mNumbers[i][j]) {
                        break
                    }

                    // 两数相等则相加
                    mNumbers[i][k] += mNumbers[i][j]
                    mNumbers[i][j] = 0
                    ignore = k
                    changed = true
                }
            }

            // 所有数字向右移动
            for (j in Const.GAME_BOARD_GRID_COUNT_H - 2 downTo 0) {
                // 如果该数为零（没有数），则什么也不做
                if (mNumbers[i][j] == 0) {
                    continue
                }

                // 不为零则向右移动
                for (k in j + 1 ..< Const.GAME_BOARD_GRID_COUNT_H) {
                    if (mNumbers[i][k] == 0) {
                        mNumbers[i][k] = mNumbers[i][k - 1]
                        mNumbers[i][k - 1] = 0
                        changed = true
                    }
                }
            }
        }

        this.afterMove(changed)
    }

    /**
     * 上移。
     */
    private fun moveUp() {
        var ignore = -1 // 忽略的格
        var changed = false // 是否相加、移动

        // 从左到右遍历每一列
        for (j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H) {
            // 每列从下到上遍历每一格，判断是否有相同数，有则相加
            for (i in Const.GAME_BOARD_GRID_COUNT_V - 1 downTo 1) {
                // 如果该数为零或之前被加过，则什么也不做
                if (mNumbers[i][j] == 0 || i == ignore) {
                    continue
                }

                // 不为零则从该数上方逐个检查
                for (k in i - 1 downTo 0) {
                    // 遇到零跳过
                    if (mNumbers[k][j] == 0) {
                        continue
                    }

                    // 如果两数不相等，则结束本次循环
                    if (mNumbers[k][j] != mNumbers[i][j]) {
                        break
                    }

                    // 两数相等则相加
                    mNumbers[k][j] += mNumbers[i][j]
                    mNumbers[i][j] = 0
                    ignore = k
                    changed = true
                }
            }

            // 所有数字向上移动
            for (i in 1 ..< Const.GAME_BOARD_GRID_COUNT_V) {
                // 如果该数为零（没有数），则什么也不做
                if (mNumbers[i][j] == 0) {
                    continue
                }

                // 不为零则向上移动
                for (k in i - 1 downTo 0) {
                    if (mNumbers[k][j] == 0) {
                        mNumbers[k][j] = mNumbers[k + 1][j]
                        mNumbers[k + 1][j] = 0
                        changed = true
                    }
                }
            }
        }

        this.afterMove(changed)
    }

    /**
     * 游戏开始。
     */
    val start = fun() {
        // 数字矩阵清零
        for (i in 0 ..< Const.GAME_BOARD_GRID_COUNT_V) {
            for (j in 0 ..< Const.GAME_BOARD_GRID_COUNT_H) {
                mNumbers[i][j] = 0
            }
        }

        // 创建随机数字
        this.createRandomNumber()

        // 将状态设置为游戏中
        mStatus.value = Status.PLAYING
    }

    /**
     * 获取游戏状态。
     */
    val status: Status
        get() = mStatus.value
}
